App.require(["htmlTool", "canvasTool"], function (htmlTool, canvasTool) {
    var app = App.getCurrent();

    app.defineLayer("str-funnel-chart", {
        render: function (next) {
            this.initProperty();
            this.data.isChange = true;
            this.data.isMouseIn = false;
            this.fetch("./index.html", function (text) {
                next(this.renderHTML(text));
            })
        },
        removeOption: function (index) {
            this.data.options.splice(index, 1);
            this.data.isChange = true;
            var property = this.property;
            if (property.animation) this.addPropertyAnimation(property.animation, this);
            else this.addChange();
        },
        appendOption: function (option) {
            this.data.options.push(option);
            this.data.isChange = true;
            var property = this.property;
            if (property.animation) this.addPropertyAnimation(property.animation, this);
            else this.addChange();
        },
        insertBefore: function (index, option) {
            this.data.options.splice(index, 0, option);
            this.data.isChange = true;
            var property = this.property;
            if (property.animation) this.addPropertyAnimation(property.animation, this);
            else this.addChange();
        },
        changeOption: function (index, opt) {
            var option = this.data.options[index];
            if (!option) return;
            this.data.isChange = true;
            this.addChange(opt, option);
        },
        _resort: function () {
            this.data.options = this.data.options.sort(function (prev, next) {
                var result = parseFloat(next.value) - parseFloat(prev.value);
                if (result > 0) return 1;
                return -1;
            });
        },
        initProperty: function () {
            var property = this.property;

            this.data.attributeStr = this._createPropertyStr(property);
            this.data.options = htmlTool.getOptions(this.slot);
            if (property.animation) this.addPropertyAnimation(property.animation, this);
        },
        clickLayerHandler: function (ev, ctx, ratio) {
            var lens = this._createWidths(this.data.options, this.fwidth, this.percent);
            var points = this._createPoints(lens, this.fheight, this.distance, this.direction);
            for (var i = 0; i < points.length; i++) {
                this._createPointsPath(ctx, points[i], ratio);
                var index = points.length - 1 - i;
                if (ctx.isPointInPath(ev.clientX * ratio, ev.clientY * ratio)) {
                    this.dispatchCustomEvent("funnelchartclick", {
                        component: this,
                        event: ev,
                        index: index,
                        data: this.data.options[index]
                    });
                    break;
                }
            }
        },
        moveLayerHandler: function (ev, ctx, ratio) {
            var lens = this._createWidths(this.data.options, this.fwidth, this.percent);
            var points = this._createPoints(lens, this.fheight, this.distance, this.direction), 
                isMouseIn = false;
            for (var i = 0; i < points.length; i++) {
                var index = points.length - 1 - i;
                this._createPointsPath(ctx, points[i], ratio);
                if (ctx.isPointInPath(ev.clientX * ratio, ev.clientY * ratio)) {
                    isMouseIn = true;
                    if (!this.data.isMouseIn) {
                        this.data.isMouseIn = true;
                        this.data.index = index;
                        this.dispatchCustomEvent("funnelchartmouseenter", {
                            component: this,
                            event: ev,
                            index: index,
                            data: this.data.options[index]
                        });
                        break;
                    }
                }
            }

            if (this.data.isMouseIn && !isMouseIn) {
                this.data.isMouseIn = false;
                this.dispatchCustomEvent("funnelchartmouseleave", {
                    component: this,
                    event: ev,
                    index: this.data.index,
                    data: this.data.options[this.data.index]
                });
            }
        },
        transformView: function (ctx, ratio) {
            if (this.transform) {
                var tList = canvasTool.createTransformList(this.transform, ratio);
                for (var i = 0; i < tList.length; i++) {
                    var tObj = tList[i];
                    ctx[tObj.action].apply(ctx, tObj.args);
                }
            }
        },
        drawSelf: function (ctx, ratio) {
            var property = this.property;
            if (this.hidden || this.data.options.length == 0) return;
            this._resort();
            var dObj = this.getPropertyUse(["percent", "distance", "fheight", "fwidth", "dlength", "opacity", "x", "y", "shadow", "linewidth", "linedash", "linedashoffset", "shadow", "fill", "stroke"]);
            if (typeof dObj.opacity !== "undefined") ctx.globalAlpha = parseFloat(dObj.opacity);
            ctx.translate(dObj.x * ratio, dObj.y * ratio);
            this.transformView(ctx, ratio);
            var keyToCanvas = canvasTool.keyToCanvas;
            for (var key in property) {
                var ctxKey = keyToCanvas[key];
                if (ctxKey) {
                    if (ctxKey == "fillStyle" || ctxKey == "strokeStyle") continue;
                    else ctx[ctxKey] = this[key];
                }
            }
            if (dObj.linedash) this.useLineDash(ctx, dObj.linedash, dObj.linedashoffset, ratio);
            if (dObj.shadow) this.useShadow(ctx, dObj.shadow, ratio);
            var lens = this._createWidths(this.data.options, dObj.fwidth, dObj.percent);
            var points = this._createPoints(lens, dObj.fheight, dObj.distance, this.direction);

            for (var i = 0; i < points.length; i++) {
                var obj = this.data.options[i];
                ctx.save();
                var keyToCanvas = canvasTool.keyToCanvas;
                for (var key in obj) {
                    var ctxKey = keyToCanvas[key];
                    if (ctxKey) {
                        if (ctxKey == "fillStyle" || ctxKey == "strokeStyle") continue;
                        else ctx[ctxKey] = obj[key];
                    }
                }
                if (obj.linedash) this.useLineDash(ctx, obj.linedash, obj.linedashoffset, ratio);
                if (obj.shadow) this.useShadow(ctx, obj.shadow, ratio);
                
                if (obj.fill || dObj.fill) {
                    ctx.save();
                    this._createPointsPath(ctx, points[i], ratio);
                    if (obj.shadow || dObj.shadow) {
                        ctx.fillStyle = "rgba(255, 255, 255, 0)";
                        ctx.fill();
                    }
                    ctx.fillStyle = this.getFillOrStrokeStyle(obj.fill || dObj.fill);
                    ctx.clip();
                    this._createFillPath(ctx, points[i], dObj.dlength, this.direction, ratio);
                    ctx.fill();
                    ctx.restore();
                }
                if (obj.stroke || dObj.stroke) {
                    this._createPointsPath(ctx, points[i], ratio);
                    ctx.strokeStyle = this.getFillOrStrokeStyle(obj.stroke || dObj.stroke);
                    ctx.stroke();
                }
                ctx.restore();
            }
        },
        _createFillPath: function (ctx, points, glen, direction, ratio) {
            var rect = this._getRect(points);
            ctx.translate(rect.x * ratio, rect.y * ratio);
            switch (direction) {
                case "row":
                case "row-reverse":
                    ctx.scale(rect.width / glen, 1);
                    ctx.beginPath();
                    ctx.rect(0, 0, glen * ratio, rect.height * ratio);
                    break;
                case "column":
                case "column-reverse":
                    ctx.scale(1, rect.height / glen);
                    ctx.beginPath();
                    ctx.beginPath();
                    ctx.rect(0, 0, rect.width * ratio, glen * ratio);
                    break;
            }
        },
        _getRect: function (points) {
            var prevPoint = { x: Infinity, y: Infinity }, nextPoint = { x: -Infinity, y: -Infinity };
            for (var i = 0; i < points.length; i++) {
                var p = points[i];
                if (prevPoint.x > p.x) prevPoint.x = p.x;
                if (prevPoint.y > p.y) prevPoint.y = p.y;
                if (nextPoint.x < p.x) nextPoint.x = p.x;
                if (nextPoint.y < p.y) nextPoint.y = p.y;
            }
            return {
                x: prevPoint.x,
                y: prevPoint.y,
                width: nextPoint.x - prevPoint.x,
                height: nextPoint.y - prevPoint.y
            };
        },
        _createPointsPath: function (ctx, points, ratio) {
            ctx.beginPath();
            ctx.moveTo(points[0].x * ratio, points[0].y * ratio);
            for (var i = 1; i < points.length; i++) {
                ctx.lineTo(points[i].x * ratio, points[i].y * ratio);
            }
            ctx.closePath();
        },
        _createPoints: function (lens, len, distance, direction) {
            var p = len / distance, result = [], x = 0, y = 0;
            for (var i = 1; i < lens.length; i++) {
                var cl = lens[i - 1], pl = lens[i], pnums = [];
                var plist = this._createPointList(x, y, cl, pl, direction, distance);
                pnums.push(plist[0]);
                pnums.push({ x: (1 - p) * plist[0].x + p * plist[2].x, y: (1 - p) * plist[0].y + p * plist[2].y })
                pnums.push({ x: (1 - p) * plist[1].x + p * plist[3].x, y: (1 - p) * plist[1].y + p * plist[3].y })
                pnums.push(plist[1]);
                var stepObj = this.stepDistance(distance, direction);
                x += stepObj.x;
                y += stepObj.y;
                result.push(pnums);
            }
            return result;
        },
        _createPointList: function (x, y, cl, pl, direction, distance) {
            switch (direction) {
                case "row-reverse":
                    return [
                        { x: x, y: y - cl / 2 },
                        { x: x, y: y + cl / 2 },
                        { x: x - distance, y: y - pl / 2 },
                        { x: x - distance, y: y + pl / 2 }
                    ];
                case "row":
                    return [
                        { x: x, y: y - cl / 2 },
                        { x: x, y: y + cl / 2 },
                        { x: x + distance, y: y - pl / 2 },
                        { x: x + distance, y: y + pl / 2 }
                    ];
                case "column":
                    return [
                        { y: y, x: x - cl / 2 },
                        { y: y, x: x + cl / 2 },
                        { y: y + distance, x: x - pl / 2 },
                        { y: y + distance, x: x + pl / 2 }
                    ];
                case "column-reverse":
                    return [
                        { y: y, x: x - cl / 2 },
                        { y: y, x: x + cl / 2 },
                        { y: y - distance, x: x - pl / 2 },
                        { y: y - distance, x: x + pl / 2 }
                    ];
            }
            return result;
        },
        stepDistance: function (distance, direction) {
            switch (direction) {
                case "row": return { x: distance, y: 0 };
                case "row-reverse": return { x: -distance, y: 0 };
                case "column": return { x: 0, y: distance };
                case "column-reverse": return { x: 0, y: -distance };
            }
        },
        _createWidths: function (options, maxWidth, percent) {
            var numList = options.map(function (obj) {
                return parseFloat(obj.value);
            });

            var con = maxWidth / numList[0];
            var calcList = [ 0 ];
            for (var i = numList.length - 1; i >= 0; i--) {
                calcList.push(numList[i] * con * percent);
            }
            return calcList;
        },
        dispose: function () {
            this.removeAnimation();
            this.data.isChange = true;
            this.data.isMouseIn = false;
        },
        get direction() {
            return this.getValue("direction");
        },
        set direction(value) {
            this.setValue("direction", value);
        },
        get distance() {
            return this.getValue("distance");
        },
        set distance(value) {
            this.setValue("distance", value);
        },
        get fheight() {
            return this.getValue("fheight");
        },
        set fheight(value) {
            this.setValue("fheight", value);
        },
        get fwidth() {
            return this.getValue("fwidth");
        },
        set fwidth(value) {
            this.setValue("fwidth", value);
        },
        get fill() {
            return this.getValue("fill");
        },
        set fill(value) {
            this.setValue("fill", value);
        },
        get stroke() {
            return this.getValue("stroke");
        },
        set stroke(value) {
            this.setValue("stroke", value);
        },
        get linedash() {
            return this.getValue("linedash(");
        },
        set linedash(value) {
            this.setValue("linedash(", value);
        },
        get linedashoffset() {
            return this.getValue("linedashoffset");
        },
        set linedashoffset(value) {
            this.setValue("linedashoffset", value);
        },
        get linewidth() {
            return this.getValue("linewidth");
        },
        set linewidth(value) {
            this.setValue("linewidth", value);
        },
        get transform() {
            return this.getValue("transform");
        },
        set transform(value) {
            this.setValue("transform", value);
        },
        get shadow() {
            return this.getValue("shadow");
        },
        set shadow(value) {
            this.setValue("shadow", value);
        },
        get hidden() {
            return this.getValue("hidden");
        },
        set hidden(value) {
            this.setValue("hidden", value);
        },
        get x() {
            return this.getValue("x");
        },
        set x(value) {
            this.setValue("x", value);
        },
        get y() {
            return this.getValue("y");
        },
        set y(value) {
            this.setValue("y", value)
        },
        get percent() {
            return this.getValue("percent");
        },
        set percent(value) {
            this.setValue("percent", value)
        },
        get animation() {
            return this.getValue("animation");
        },
        set animation(value) {
            if (this.setValue("animation", value)) {
                if (value) this.addPropertyAnimation(value, this);
            }
        },
        set transition(value) {
            this.setValue("transition", value);
        },
        get transition() {
            return this.getValue("transition");
        },
        set opacity(value) {
            this.setValue("opacity", value);
        },
        get opacity() {
            return this.getValue("opacity");
        },
        get dlength() {
            return this.getValue("dlength");
        },
        set dlength(value) {
            return this.setValue("dlength", value);
        }
    })
})